package com.myeasyexcel.utils;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.myeasyexcel.constant.CustomException;
import com.myeasyexcel.response.ExcelCode;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author wangXiaoHu
 * @date 2021/12/15 17:05
 **/
@Slf4j
@Data
public abstract class EasyExcelListenerPlus<T extends ExcelErrMsg> extends AnalysisEventListener<T> {
    /**
     * 每隔5条存储数据库，实际使用中可以100条，然后清理list ，方便内存回收
     */
    private int batchCount;

    /**
     * 缓存的数据
     */
    private Set<T> set = new HashSet<>(batchCount);

    /**
     * 成功结果数量
     */
    private int successSize;

    /**
     * 失败结果集
     */
    private List<T> errList = new ArrayList<>();

    /**
     * 表头
     */
    private volatile Map<Integer,String> head;

    /**
     * T类型的Class对象
     */
    private Class tClass;


    public EasyExcelListenerPlus(int batchCount) {
        this.batchCount = batchCount;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param t       每行数据
     * @param context
     */
    @SneakyThrows
    @Override
    public void invoke(T t, AnalysisContext context) {
        if(this.allEntityfieldIsNull(t)){
            return;
        }
        log.info("解析到一条数据:{}", JSON.toJSONString(t));
        String errMsg;
        try {
            // 根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
            errMsg = EasyExcelValiHelper.validateEntity(t);
            if(StringUtils.isNotBlank(errMsg)){
                t.setErrMsg(errMsg);
                errList.add(t);
                return;
            }
            errMsg = otherParamCheck(t, errMsg);
        } catch (NoSuchFieldException e) {
            errMsg = "解析数据出错";
            e.printStackTrace();
        }
        // 封装错误数据
        if (!StringUtils.isEmpty(errMsg)) {
            // 将错误数据放进errList【都是null的不算错误数据】
            t.setErrMsg(errMsg);
            errList.add(t);
        } else {
            // 正确的数据 有的数据全部为null,视为无效数据
            set.add(t);
        }
        //每 batchCount 条处理一次
        if (set.size() > batchCount) {
            // 去数据库层校验，将合法的数据入库，返回成功和失败的数据
            log.info(JSONObject.toJSONString(set));
            ExcelCheckResult<T> result = dbOperateReturnData(set);
            successSize+=result.getSuccessSize();
            errList.addAll(result.getErrDtos());
            set.clear();
        }
    }

    /**
     * 判断对象非静态属性是否全部为空
     * @param o
     * @return 全部为null，返回true。
     */
    private boolean allEntityfieldIsNull(Object o){
        try{
            for(Field field:o.getClass().getDeclaredFields()){
                // 判断属性是否是静态的
                if(Modifier.isStatic (field.getModifiers())){
                    continue;
                }
                //把私有属性公有化
                field.setAccessible(true);
                Object object = field.get(o);
                if(object instanceof CharSequence){
                    if(!StringUtils.isEmpty((String)object)){
                        return false;
                    }
                }else{
                    if(!Objects.isNull(object)){
                        return false;
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 表头解析会来调用
     *
     * @author duanyangyang
     * @date 2022/4/7 11:46
     * @param
     * @result
     */
    @SneakyThrows
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        //标题行定位
        int currentHeadRowNumber = context.readSheetHolder().getHeadRowNumber();
        int rowIndex = context.readRowHolder().getRowIndex();
        if(rowIndex + 1 < currentHeadRowNumber){
            return;
        }else if(rowIndex + 1 > currentHeadRowNumber){
            throw new CustomException(ExcelCode.TEMPLATE_ROW_NAME_ERROR);
        }
        //根据传入的tClass填充head
        if (null == head) {
            head = getIndexNameMap(tClass);
        }
        if (MapUtils.isEmpty(head)) {
            return;
        }
        //校验 headMap 是否与 head 匹配
        Set<Integer> keySet = head.keySet();
        for (Integer key : keySet) {
            if (StringUtils.isEmpty(headMap.get(key))) {
                throw new CustomException(ExcelCode.TEMPLATE_ROW_NAME_ERROR);
            }
            if (!headMap.get(key).equals(head.get(key))) {
                throw new CustomException(ExcelCode.TEMPLATE_ROW_NAME_ERROR);
            }
        }
    }

    private Map<Integer,String> getIndexNameMap(Class clazz) throws NoSuchFieldException {
        Map<Integer,String> result = new HashMap<>();
        Field field;
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            field = clazz.getDeclaredField(fields[i].getName());
            field.setAccessible(true);
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            if (excelProperty != null) {
                int index = excelProperty.index();
                String[] values = excelProperty.value();
                StringBuilder value = new StringBuilder();
                for (String v : values) {
                    value.append(v);
                }
                result.put(index, value.toString());
            }
        }
        return result;
    }

    /**
     * 其他的参数校验，例如时间在当前日期之前
     *
     * @param t
     * @param errMsg
     * @return
     */
    public abstract String otherParamCheck(T t, String errMsg);

    /**
     * 数据库层校验，成功的数据批量入库，并返回成功list和失败list
     *
     * @param set 需要批处理的set集合
     * @return
     */
    public abstract ExcelCheckResult<T> dbOperateReturnData(Set set);

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 去数据库层校验，将合法的数据入库，返回成功和失败的数据
        ExcelCheckResult<T> result = dbOperateReturnData(set);
        successSize += result.getSuccessSize();
        errList.addAll(result.getErrDtos());
        set.clear();
        log.info("所有数据解析完成！");
    }
}
